Назад к обновлениям
UpdatedAug 8, 2026

foxcage — Updated!

Запустите Firefox в rootless-контейнере Podman с удалёнными capabilities, изолированной сетью и эфемерным хранилищем, чтобы локализовать побеги из песочницы и предотвратить компрометацию хоста.

Поделиться

foxcage icon foxcage

Запускайте Firefox в rootless-контейнере Podman для изоляции от системы. Ваш браузер работает практически без Linux-возможностей (capabilities), в собственном пользовательском и сетевом пространстве имён, изолированно от хоста — при этом сохраняя полное аппаратное ускорение GPU, звук и поддержку DRM.

Зачем foxcage?

У Firefox уже есть многопроцессный песочница, которая изолирует рендереры веб-контента с помощью пространств имён Linux и seccomp-bpf. Для большинства угроз этого достаточно. foxcage добавляет вторую стену: если злоумышленник эксплуатирует уязвимость, позволяющую выйти из песочницы Firefox (такое случается — для этого существуют CVE), он попадает в изолированный контейнер, а не в ваш полный пользовательский сеанс.

От чего защищает foxcage

  • Доступ к файлам после эксплуатации. Побег из песочницы в обычном Firefox даёт доступ ко всему, что может прочитать ваш пользователь: ~/.ssh, ~/.gnupg, профили браузеров для других браузеров, базы данных менеджеров паролей, документы, исходный код. В foxcage атакующий видит только то, что вы явно смонтировали внутрь.
  • Остаточные следы на диске. Эфемерная клетка @tmp не оставляет на диске никаких следов после закрытия окна — включая расширения, HSTS-состояние, кэш TLS-сессий и DNS-кэш, которые приватный режим Firefox всё равно сохраняет. Несколько клеток @tmp работают одновременно, не мешая друг другу.
  • Постоянство. В обычном Firefox вредоносное ПО может записаться в ~/.config/autostart, ~/.bashrc, cron или куда угодно ещё, чтобы пережить перезагрузку. Эфемерный контейнер foxcage (--rm) означает, что ничего не сохраняется, если вы не смонтировали это через bind.
  • Латеральное перемещение по сети. По умолчанию контейнер не может опрашивать сервисы на localhost. В обычном Firefox побег из песочницы имеет полный сетевой доступ. (Используйте [network] mode = "host", если клетке нужен доступ к localhost, например для локальной разработки — но см. предостережение в разделе «Сеть»: host-режим также открывает abstract Unix-сокеты хоста.)
  • Повышение привилегий. Контейнер сбрасывает все Linux-возможности, кроме CAP_SYS_CHROOT, и блокирует получение новых привилегий. Suid-бинарники, эксплойты ядра через малоизвестные syscall'ы и подобные пути повышения привилегий отсечены.

От чего foxcage не защищает

  • Атаки на уровне браузера. Фишинг, вредоносные расширения и всё, что действует в рамках обычной функциональности Firefox, остаётся без изменений — foxcage изолирует контейнер от хоста, а не пользователя от браузера.
  • Смонтированные через bind каталоги. Всё, что вы монтируете внутрь (profile, downloads_dir, дополнительные bind-монтирования), полностью доступно скомпрометированному браузеру. Если вы монтируете профиль с хоста, атакующий сможет вмешиваться в него так же, как в обычном Firefox.
  • Запись звука через PulseAudio. Сокет PulseAudio монтируется в контейнер через bind. Хотя на уровне файловой системы он смонтирован только для чтения, Unix-доменные сокеты двунаправленны — скомпрометированный процесс всё равно может отправлять через сокет запросы на запись. Побег из песочницы браузера потенциально может записывать звук с микрофона хоста.
  • Эксплойты композитора Wayland. Сокет Wayland передаётся внутрь. Композиторы Wayland по замыслу изолируют клиентов друг от друга, но уязвимость в самом композиторе будет достижима.

Конфигурация безопасности

Контейнер запускается со следующими параметрами:

  • Все Linux-возможности сброшены (добавлен только CAP_SYS_CHROOT для песочницы контента Firefox; CAP_SETUID/CAP_SETGID временно добавляются, когда настроен init.root)
  • no-new-privileges для предотвращения повышения привилегий
  • Rootless-пространство имён пользователя (--userns keep-id)
  • Отдельный /dev/shm (не общий с хостом) — размер настраивается через shm_size
  • Изолированная сеть через pasta с блокировкой host loopback по умолчанию
  • DNS использует DNS хоста по умолчанию (настраивается через network.dns)
  • Внутрь монтируются только определённые сокеты из XDG_RUNTIME_DIR (Wayland, PulseAudio, PipeWire и фильтрованный D-Bus-прокси) — полный каталог runtime хоста никогда не открывается
  • Доступ к сессионной шине 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 нет правила «запретить один интерфейс», поэтому сужение этого означает перечисление всех интерфейсов, необходимых Firefox; см. docs/DESIGN.md о том, почему это не сделано по умолчанию
  • Все bind-монтирования (profile, downloads_dir, дополнительные [mounts] bind) используют nosuid,noexec
  • Загрузка браузера проверяется по GPG-подписям: Firefox — по подписанным SHA-512-контрольным суммам Mozilla, LibreWolf — по отдельной подписи сопровождающих LibreWolf плюс соседним SHA-256. Проверка строже, чем gpg --verify, который возвращает 0 для подписи, сделанной отозванным ключом, и для любого ключа из связки. foxcage дополнительно требует, чтобы подпись восходила к закреплённому основному ключу, и отказывается принимать любой релиз, подписанный субключом, владелец которого отозвал его как скомпрометированный — см. Отозванные ключи подписи
  • Эфемерный контейнер (--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 или PipeWire с совместимостью с PulseAudio (для звука)
  • GPU с поддержкой DRI — необязательно; без /dev/dri foxcage выдаёт предупреждение, а Firefox рендерит программно

Запускайте foxcage от имени обычного пользователя рабочего стола, а не от root или через sudo — песочница отображает вашего пользователя внутрь контейнера, а запуск от root уничтожает ту изоляцию, ради которой существует foxcage. Он отказывается запускаться от root.

Протестированное окружение: Debian 13 (Trixie) с GNOME 3. Другие дистрибутивы 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

При первом запуске скрипт собирает образ контейнера (загружает Firefox от Mozilla, устанавливает минимальные зависимости Debian) и затем запускает Firefox. При последующих запусках foxcage проверяет наличие обновлений Firefox и автоматически пересобирает образ, когда доступна новая версия. Образ также пересобирается периодически (по умолчанию каждые 7 дней) для получения обновлений системных пакетов. Если проверка обновлений завершается с ошибкой (сетевая ошибка, тайм-аут), выводится предупреждение в журнал и используется существующий образ — запуск никогда не блокируется.

Передача аргументов в Firefox:```sh
./foxcage https://example.com

Объедините именованную клетку с флагами Firefox:```sh ./foxcage @work --kiosk https://example.com

Если cage уже запущен, URL открывается в новой вкладке в существующем браузере, а не запускает второй контейнер. Запуск `foxcage` (или `foxcage @cage`) без URL при уже запущенном cage завершается чисто с сообщением "cage is already running" — foxcage не может поднять существующее окно Wayland извне контейнера, поэтому и не пытается.

Флаги, задаваемые при запуске, **не** применяются, если cage уже запущен. `--dns`, `--ipv4-only`, `--lifetime`, `--color` и `--fork` потребляются при старте контейнера, а настройки работающего контейнера нельзя изменить извне, поэтому они игнорируются с предупреждением. Закройте cage и запустите заново, чтобы применить их.

> Используйте ключ конфигурации `private_browsing` для сеансов приватного режима — *не* сырой флаг CLI Firefox `--private-window`. Ключ конфигурации задаёт приватный режим для всего сеанса (`browser.privatebrowsing.autostart`), поэтому последующие вызовы `foxcage @cage URL` могут открывать ссылки повторно во вкладках. `--private-window` в качестве прямой передачи в Firefox сделал бы приватным только первое окно и сломал бы описанное выше поведение повторного открытия во вкладках.
>
> **Внимание:** сеансы, включённые через `private_browsing = true`, не показывают обычные индикаторы приватного окна Firefox (фиолетовую акцентную полосу, значок маски, "(Приватный просмотр)" в заголовке). Это потому, что каждое окно в сеансе приватно, и Firefox не может визуально сопоставить его с неприватным окном — поэтому он скрывает индикатор. Сеанс *действительно* приватный; можете проверить, если хотите, посетив `about:privatebrowsing` в cage (откроется стандартная страница с информацией о приватном просмотре) или `about:config` и проверив `browser.privatebrowsing.autostart = true`.

### Временный просмотр с `@tmp`

Для одноразовых ссылок, которые не должны оставлять следов, используйте зарезервированный cage `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` в том, что повторные запуски с тем же именем **повторно используют существующее окно** (как постоянные клетки), поэтому вы можете добавлять новые вкладки позже, не запуская параллельную копию. Простой `@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. Если нужна песочница с постоянным профилем, используйте обычную именованную клетку (@work, @research и т.п.), имя которой не начинается с tmp-.

Переопределение DNS при запуске

Флаг --dns (и эквивалентный ключ конфига network.dns) принимает три формы:```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 (строгий, без резервного перехода на незашифрованный канал), а bootstrap-адрес заполняется, чтобы при запуске не было утечки незашифрованного разрешения. Вы увидите однострочное уведомление в stderr, например: `Enabling DNS over HTTPS via Cloudflare`.

Встроенные алиасы:

| 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-адрес, которого нет в таблице (например, ваш Pi-hole в локальной сети), остаётся только в незашифрованном виде — DoH не включается, поскольку foxcage не знает соответствующую DoH-конечную точку. Используйте для этого форму URI: `--dns https://pi.hole/dns-query` (с действующим сертификатом) включает DoH и не трогает DNS контейнера.

Форма URI не задаёт незашифрованный DNS контейнера, поэтому всё внутри контейнера, кроме Firefox, по-прежнему использует DNS хоста. Это сделано намеренно: `--dns URI` означает «заставить Firefox использовать этот DoH-резолвер», и всё.

`--dns` несовместим с `network.mode = "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 фактически запускается внутри клетки — время запуска контейнера и сборки образа не тратится из вашего бюджета. На метке в строке меню клетки отображается обратный отсчёт вместе с идентификатором клетки — например, FoxCage - tmp (a3f2b1) | 9m — обновляется раз в минуту, пока осталось больше минуты, и раз в секунду в последнюю минуту. Когда отсчёт достигает нуля, Firefox закрывается сам, и контейнер завершает работу. Если вы закроете Firefox самостоятельно до истечения срока жизни, ничего необычного не произойдёт.

Задайте срок жизни по умолчанию для каждой клетки в её конфигурации:```toml

~/.config/foxcage/tmp.toml — every @tmp launch auto-closes after 15 minutes

lifetime = "15m" private_browsing = true

`--lifetime` в командной строке имеет приоритет над любым значением конфигурации.

Принудительно выполните полную пересборку образа (повторно загрузит Firefox и все системные пакеты):```sh
./foxcage --rebuild

Запущенный контейнер сохраняет образ, из которого он был запущен, даже после того, как foxcage пересоберёт тег образа. Если вы попытаетесь открыть вкладку в cage, чей образ с тех пор был обновлён (с помощью --rebuild, обновления Firefox или запланированной пересборки), foxcage отказывается с ошибкой (также показанной в виде уведомления на рабочем столе) и просит вас закрыть Firefox и перезапустить его — это запустит свежий контейнер на текущем образе. При использовании --rebuild с активным cage foxcage заранее предупреждает, выполняет сборку, а затем применяет ту же проверку.

Обновления

foxcage проверяет наличие новых версий браузеров при каждом запуске — через релизный API Mozilla для Firefox и через endpoint релизов GitLab для LibreWolf. Если доступно обновление, образ контейнера пересобирается автоматически. Образ также пересобирается периодически (по умолчанию каждые 7 дней), чтобы получить обновления безопасности Debian. Встроенный автоматический обновитель браузера отключён, поскольку обновления обрабатываются на уровне образа.

Если проверка обновлений не удалась (нет сети, таймаут 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)

**Для закрепления ESR-версии также требуется указать канал.** Индекс версий Mozilla перечисляет ESR-релизы без суффикса `esr`, который присутствует в их загрузках, поэтому простое `version = "140"` на канале по умолчанию приводит к несуществующему релизу. Задайте оба:```toml
[firefox]
channel = "esr"
version = "140"        # → 140.13.0esr

Пин, которому не соответствует ни один релиз, теперь приводит к ошибке с указанием пина, а не тихо откатывается к последнему релизу. Временный сбой при обращении к API Mozilla по-прежнему лишь предупреждает и продолжает работу с существующим образом, поэтому нестабильная сеть никогда не блокирует запуск.

Пины с суффиксами должны быть полностью определёнными — "140.13.0esr" и "150.0b9" работают, а "140esr" и "150b9" отклоняются при загрузке конфигурации, поскольку ни один релиз не может им соответствовать. То же самое относится к ревизиям LibreWolf: "146.0.1-1" работает, а "146-1" — нет.

Чтобы принудительно выполнить немедленную полную пересборку: ./foxcage --rebuild

Отозванные ключи подписи

foxcage отказывается устанавливать сборку браузера, подпись которой была создана подписывающим субключом, отозванным вышестоящим проектом как скомпрометированный (причина отзыва по RFC 4880 0x02). gpg --verify сам по себе этого не делает: он выводит предупреждение и завершается с кодом 0, поэтому без дополнительной проверки утёкший ключ подписи всё равно подтвердил бы подлинность подделанной загрузки.

Отказ выглядит следующим образом и приводит к ошибке сборки, а не к установке:``` foxcage: REFUSING /tmp/SHA512SUMS - signed by 09BEED63F3462A2DFFAB3B875ECB6497C1A20256, which its owner revoked as compromised. This build cannot be trusted; wait for upstream to re-sign this release with a current key.

Настраивать здесь нечего, и переопределения нет. Если вы столкнулись с этим, исправление — на стороне апстрима: либо закрепите релиз, подписанный актуальным ключом, либо дождитесь, пока затронутый релиз будет переподписан.

Плановая ротация ключей обрабатывается иначе. Подключ, отозванный как заменённый, выведенный из эксплуатации или без указания причины, не аннулирует подписи, сделанные *до* отзыва, поэтому такие релизы устанавливаются с предупреждением. Подпись, датированная *после* любого отзыва, отклоняется независимо от указанной причины.

**Ротация ключей Mozilla в августе 2026 года.** Mozilla отозвала подписывающий подключ `09BEED63…C1A20256` 2026-08-06 после того, как незашифрованная копия была закоммичена в приватный GitHub-репозиторий, и заменила его на `827E6586…76767AA3`. Релизы Firefox, подписанные старым подключом — всё, что между 2025-03-13 и 2026-08-06, что на момент написания всё ещё включает текущий ESR (`140.13.0esr`) и любой `version`-пин в этом окне — отклоняются описанной выше проверкой. Каналы Release и Beta не затронуты. foxcage получает ключи с `keys.openpgp.org`, а не с `keyserver.ubuntu.com`, потому что последний в течение нескольких дней после ротации не отдавал ни заменённый подключ, ни отзыв; устаревший keyserver и сразу ломал бы сборки, и незаметно сводил бы проверку отзыва к холостой операции.

### Форки 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, телеметрия отключена по умолчанию. Подписанный Linux-тарбол из GitLab (`librewolf-community/browser/bsys6`), GPG-сверенный с ключом сопровождающих LibreWolf `662E 3CDD 6FE3 2900 2D0C A5BB 4033 9DD8 2B12 EF16` и перекрёстной проверкой сопутствующего `.sha256sum`, по тем же [правилам отзыва](#revoked-signing-keys), что и Firefox. Встроенный `librewolf.cfg` LibreWolf сохраняется; foxcage добавляет свои prefs поверх, а не перезаписывает. Сопровождающие сменили подписывающий подключ 2026-04-25 без объяснения причин; текущие тарболы были подписаны до этой даты, поэтому они устанавливаются с предупреждением, а не отклоняются.

**Канал доступен только для Firefox**: `firefox.channel = "beta" | "esr"` отклоняется, если `fork` имеет любое значение, кроме `"firefox"`. У LibreWolf только один релизный канал.

Переключение `fork` (через конфиг или `--fork`) изменяет хэш Containerfile, что вызывает пересборку при следующем запуске — ручной `--rebuild` не требуется.

#### Совместимость профилей

> **Используйте отдельный профиль для каждого форка.** Самый безопасный вариант по умолчанию — позволить foxcage создать собственный профиль (опустите `profile` в конфиге) или указать `profile` на каталог, который вы не открываете также с хоста.

- **LibreWolf**: *обычно* нормально использовать совместно с вашим профилем Firefox на хосте — LibreWolf обычно отстаёт от версий Firefox не более чем на несколько дней, поэтому конфликты схемы `compatibility.ini` редки. Риски: (1) безопасно только последовательное использование (файл блокировки Firefox предотвращает одновременные запуски); (2) в коротком окне после стабильного релиза Firefox запуск сначала Firefox, а затем LibreWolf может вызвать диалог миграции «используется более новой версией»; (3) функции, которые LibreWolf удаляет (Sync, Pocket, аккаунт Mozilla), молча не работают, но не портят данные.

### Именованные клетки

Запуск отдельных изолированных экземпляров с собственным конфигом и профилем 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 ( 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 "-",

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` путь к каталогу профиля. Найдите путь к своему профилю, посетив `about:profiles` в Firefox на хосте — или просто укажите новый пустой каталог, если вы хотите, чтобы кадж запускался с чистым профилем, который сохраняется на хосте.```toml
profile = "~/.mozilla/firefox/xxxxxxxx.default-release"

В клетку выполняется bind-mount только этого каталога. Соседние профили в ~/.mozilla/firefox/ и реестр profiles.ini недоступны — скомпрометированная клетка не сможет ими манипулировать.

Если profile не задан, именованный том Podman хранит профиль Firefox вместо этого (см. «Что сохраняется» ниже). Если тот же профиль уже открыт в Firefox на хосте, файл блокировки профиля Firefox вызовет конфликт — используйте отдельный профиль для каждой клетки.

Сеть

По умолчанию контейнер использует pasta с заблокированным loopback хоста и DNS хоста. Для pasta требуется podman 4.4 или новее (он является стандартным для rootless начиная с podman 5.0).

Host networking полностью устраняет сетевую изоляцию. Используйте это, когда клетке нужно обращаться к сервисам на localhost хоста (например, локальный dev-сервер, база данных на 127.0.0.1):```toml [network] mode = "host"

`dns` нельзя комбинировать с `mode = "host"` — host-сеть уже использует резолвер хоста.

> **Host-режим жертвует большим, чем localhost.** Он помещает клетку в сетевое пространство имён хоста, а абстрактные Unix-сокеты привязаны к этому пространству имён, а не к файловой системе. Поэтому клетка в host-режиме может напрямую обращаться к сокетам с абстрактными адресами на хосте — включая сокет Xwayland `@/tmp/.X11-unix/X0`, если вы запускаете X11 или Xwayland (логирование ввода, несмотря на то что foxcage поддерживает только Wayland), и сессионную шину, настроенную с `unix:abstract=…`, что позволило бы обойти фильтрующий D-Bus-прокси. Это неотъемлемо от совместного использования сетевого стека, и foxcage не может это отфильтровать. Используйте host-режим, когда он вам нужен, и предпочитайте именованную клетку, которую вы запускаете только для этой цели.

**IPv4-only клетки** полностью отключают IPv6:```toml
[network]
ipv4_only = true

Или при запуске с флагом --ipv4-only (короткая форма -4, как в ssh/curl/pasta):```sh ./foxcage @tmp -4 https://example.com

Это запускает pasta в режиме только IPv4 (`-4`), поэтому контейнер вообще не имеет стека IPv6, а также устанавливает `network.dns.disableIPv6` в Firefox, чтобы он не разрешал записи AAAA — это важно, когда включён DoH, поскольку ответы DoH обходят резолвер контейнера. `ipv4_only` нельзя комбинировать с `mode = "host"` — хост-сетевой режим использует сетевой стек хоста напрямую, поэтому вместо этого отключите IPv6 на хосте.

### Команды инициализации

Выполняйте пользовательские команды во время сборки или при запуске контейнера через `[init]`:

- **`build`** — выполняется во время сборки образа от имени root. Используйте для установки пакетов или других медленных настроек. Изменения в командах сборки автоматически запускают пересборку образа.
- **`root`** — выполняется при запуске контейнера от имени root, перед Firefox. Используйте для быстрых задач 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"]

Все три ключа — это списки строк shell-команд. Если любая команда завершится с ошибкой, контейнер завершает работу без запуска Firefox.

Примечание по безопасности: Когда установлен init.root, контейнер запускается от root с добавленными CAP_SETUID и CAP_SETGID (в дополнение к стандартному CAP_SYS_CHROOT), чтобы затем можно было понизить привилегии обратно до обычного пользователя. Эти capabilities удерживаются только на этапе root-инициализации — после понижения привилегий процесс обычного пользователя не имеет дополнительных capabilities. Без init.root контейнер работает с минимальным набором capabilities по умолчанию.

Что сохраняется

Без конфигурации именованный том Podman хранит профиль Firefox (закладки, настройки, расширения, плагин Widevine DRM). Всё остальное эфемерно.

  • Клетка по умолчанию: foxcage-profile
  • Именованная клетка: foxcage-<name>-profile

Чтобы начать заново, удалите том:```sh podman volume rm foxcage-profile

Если задан `profile`, каталог хоста монтируется напрямую (bind mount), и том не создаётся.

### Использование диска

Каждый образ cage имеет размер около 1 ГБ. При пересборке образ получает новый тег, а предыдущий остаётся как непомеченная запись `<none>`, поэтому foxcage после каждой успешной сборки удаляет только что вытесненный образ. Он удаляет только этот конкретный образ и никогда не удаляет образ, который всё ещё используется запущенным cage.

Образы, осиротевшие до появления этого поведения, задним числом не очищаются. Чтобы вернуть их:```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`) монтируются через bind только для чтения. Конфигурация шрифтов из `~/.config/fontconfig` также передаётся.
- **Тема GTK и тёмный режим.** Определяются через `GTK_THEME` или `gsettings` и передаются в контейнер. Конфигурация GTK из `~/.config/gtk-3.0` и `~/.config/gtk-4.0` монтируется через bind только для чтения.
- **Часовой пояс.** Название часового пояса хоста (определяется из `TZ`, символьной ссылки `/etc/localtime` или `/etc/timezone`) передаётся в контейнер как `TZ`, а `/etc/localtime` монтируется через bind только для чтения. Нужны оба: Firefox определяет часовой пояс JavaScript по *названию* зоны, а не по содержимому файла — без `TZ` сайты будут показывать время в UTC.
- **Локаль.** `LANG` передаётся. Локаль хоста генерируется в образе контейнера на этапе сборки.

**Метка клетки.** В строке меню Firefox отображается «FoxCage» (или «FoxCage - name» для именованных клеток), чтобы вы с первого взгляда могли понять, что находитесь в сеансе в контейнере. Строка меню всегда видна благодаря корпоративной политике.

Контейнер включает только тему GTK Adwaita. На рабочих столах GNOME это работает из коробки. На KDE или других рабочих столах Firefox будет использовать Adwaita в качестве запасного варианта, если ваша тема GTK (например, Breeze) не установлена в контейнере. Определение тёмного режима по-прежнему работает, если настройка задана через `gsettings` или `GTK_THEME`.

## DRM (Netflix, Disney+ и т. д.)

Widevine DRM работает из коробки. При первом посещении сайта, защищённого DRM, Firefox автоматически загрузит Widevine CDM. Это может занять некоторое время.

## Интеграция с хостом (всегда включена)

foxcage использует фильтрующий прокси D-Bus, чтобы дать Firefox доступ к XDG Desktop Portal и демону уведомлений хост-системы. Эти функции безопасны, потому что весь доступ требует участия пользователя — хост показывает нативные диалоговые окна, с которыми вы должны взаимодействовать. Скомпрометированный браузер не может незаметно получить доступ к ресурсам хоста.

- **Загрузка файлов** — нативный выборщик файлов хоста (вы выбираете, какие файлы передать)
- **Внешние ссылки** — `mailto:`, magnet-ссылки и т. д. открываются через выбор приложения на хосте
- **Уведомления рабочего стола** — пересылаются демону уведомлений хоста
- **Демонстрация экрана** — выбор экрана через портал + видеопоток 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. API Web Bluetooth, WebUSB, Web Serial и WebNFC требуют доступа к устройствам и системных служб (BlueZ, udev), которые недоступны в контейнере.

Геймпады и MIDI. API Gamepad требует доступа к /dev/input/. Web MIDI требует доступа к секвенсору ALSA. Ни то, ни другое не пробрасывается.

Установка PWA. Прогрессивные веб-приложения нельзя установить на рабочий стол хоста из контейнера.

Специальные возможности. Поддержка программ чтения с экрана через AT-SPI отключена (NO_AT_BRIDGE=1) — контейнер не имеет подключения к шине специальных возможностей хоста. Синтез Web Speech API работает: speech-dispatcher с движком espeak-ng установлен в контейнере и автоматически запускается при первом использовании, а звук маршрутизируется через общий сокет PulseAudio.

Конфигурация хоста

Рекомендуется: overlay-хранилище с fuse-overlayfs

Rootless Podman может по умолчанию использовать драйвер хранилища vfs, который копирует целые слои образов вместо использования overlay-монтирований. Это делает запуск контейнера после сборки значительно медленнее. Чтобы исправить это, установите 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 (из пакета libnotify-bin в Debian/Ubuntu) — если он не установлен, уведомления молча пропускаются, а ошибка по-прежнему выводится в stderr.

Ручная настройка

Если вы предпочитаете создать файл .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

Запуск тестов

Набор тестов использует pytest + pytest-cov, объявленные как зависимости только для разработки в requirements-dev.txt.``` pip install -r requirements-dev.txt pytest

Тесты полностью герметичны — ни podman, ни сети, ни реальной файловой системы, кроме `tmp_path` от pytest. Набор тестов требует **100% покрытия строк и веток** (настраивается в `pytest.ini` и `.coveragerc`); любая непокрытая строка или невыполненная ветка условного оператора приводит к провалу запуска. CI запускает набор тестов при каждом пуше через `.gitlab-ci.yml`.

## Благодарности

Этот проект разработан Майком Кардвеллом при содействии [Claude Code](https://claude.ai/claude-code), инструмента ИИ-кодирования от Anthropic.

## Поддержать/Оценить мою работу

- [Bitcoin](bitcoin:1PQLtWnjUi1itHLG6QCQeHM3Nxua8pRsq1): 1PQLtWnjUi1itHLG6QCQeHM3Nxua8pRsq1
- [Paypal](https://www.paypal.me/grepular)
- [Patreon](https://patreon.com/grepular)

Категории