업데이트로 돌아가기
UpdatedAug 8, 2026

foxcage — Updated!

제거된 capabilities, 격리된 네트워킹, 임시 저장소를 갖춘 루트리스 Podman 컨테이너에서 Firefox를 실행하여 샌드박스 탈출을 봉쇄하고 호스트 손상을 방지합니다.

공유

foxcage icon foxcage

보안 격리를 위해 루트리스 Podman 컨테이너에서 Firefox를 실행하세요. 브라우저는 거의 모든 Linux capability가 없는 상태로, 자체 사용자 및 네트워크 네임스페이스 안에서 호스트와 격리되어 실행됩니다 — 그러면서도 GPU 가속, 오디오, DRM 지원은 완전히 유지됩니다.

왜 foxcage인가?

Firefox에는 이미 Linux 네임스페이스와 seccomp-bpf를 사용하여 웹 콘텐츠 렌더러를 격리하는 다중 프로세스 샌드박스가 있습니다. 대부분의 위협에 대해 이는 효과적입니다. foxcage는 두 번째 벽을 추가합니다: 공격자가 Firefox의 샌드박스를 탈출하는 취약점을 악용한다면(실제로 발생하며, 이에 대한 CVE가 있습니다), 그들은 전체 사용자 세션 대신 잠긴 컨테이너 안에 들어오게 됩니다.

foxcage가 보호하는 대상

  • 익스플로잇 후 파일 접근. 순수 Firefox에서의 샌드박스 탈출은 사용자가 읽을 수 있는 모든 것에 대한 접근을 제공합니다: ~/.ssh, ~/.gnupg, 다른 브라우저의 브라우저 프로필, 비밀번호 관리자 데이터베이스, 문서, 소스 코드. foxcage에서는 공격자가 명시적으로 마운트한 것만 볼 수 있습니다.
  • 디스크 추적 잔여물. @tmp 임시 케이지는 창이 닫힌 후 디스크에 어떤 흔적도 남기지 않습니다 — Firefox의 비공개 브라우징이 여전히 유지하는 확장 프로그램, HSTS 상태, TLS 세션 캐시, DNS 캐시를 포함해서요. 여러 @tmp 케이지는 서로 간섭하지 않고 동시에 실행됩니다.
  • 지속성. 순수 Firefox에서 악성코드는 재부팅 후에도 살아남기 위해 ~/.config/autostart, ~/.bashrc, cron 또는 다른 어디든 쓸 수 있습니다. foxcage의 임시 컨테이너(--rm)는 bind-mount하지 않는 한 아무것도 지속되지 않음을 의미합니다.
  • 횡적 네트워크 이동. 기본적으로 컨테이너는 localhost의 서비스를 탐색할 수 없습니다. 순수 Firefox에서는 샌드박스 탈출 시 전체 네트워크에 접근할 수 있습니다. (케이지가 localhost 접근을 필요로 한다면 [network] mode = "host"를 사용하세요, 예: 로컬 개발 — 단, "네트워킹"의 주의사항을 참조하세요: host 모드는 호스트의 추상 Unix 소켓도 노출합니다.)
  • 권한 상승. 컨테이너는 CAP_SYS_CHROOT을 제외한 모든 Linux capability를 제거하고 새로운 권한 획득을 차단합니다. Setuid 바이너리, 난해한 syscall을 통한 커널 익스플로잇 및 유사한 권한 상승 경로는 차단됩니다.

foxcage가 보호하지 않는 대상

  • 브라우저 수준 공격. 피싱, 악성 확장 프로그램 및 Firefox의 정상 기능 내에서 작동하는 모든 것은 영향을 받지 않습니다 — foxcage는 호스트로부터 컨테이너를 격리하지, 브라우저로부터 사용자를 격리하지 않습니다.
  • Bind-mount된 디렉터리. (profile, downloads_dir, 추가 bind mounts) 등 마운트한 모든 것은 손상된 브라우저에서 완전히 접근 가능합니다. 호스트 프로필 디렉터리를 마운트하면 공격자는 순수 Firefox에서와 마찬가지로 이를 변조할 수 있습니다.
  • PulseAudio를 통한 오디오 캡처. PulseAudio 소켓은 컨테이너에 bind-mount됩니다. 파일시스템 수준에서 읽기 전용으로 마운트되지만 Unix 도메인 소켓은 양방향이므로, 손상된 프로세스는 여전히 소켓을 통해 녹음 요청을 보낼 수 있습니다. 브라우저 샌드박스 탈출로 호스트 마이크의 오디오를 녹음할 가능성이 있습니다.
  • Wayland 컴포지터 익스플로잇. Wayland 소켓이 통과됩니다. Wayland 컴포지터는 설계상 클라이언트를 서로 격리하지만, 컴포지터 자체의 취약점은 도달 가능합니다.

보안 구성

컨테이너는 다음 설정으로 실행됩니다:

  • 모든 Linux capability 제거(CAP_SYS_CHROOT만 Firefox 콘텐츠 샌드박스를 위해 다시 추가; init.root가 구성되면 CAP_SETUID/CAP_SETGID가 일시적으로 추가됨)
  • 권한 상승을 방지하는 no-new-privileges
  • 루트리스 사용자 네임스페이스(--userns keep-id)
  • 전용 /dev/shm(호스트와 공유되지 않음) — shm_size로 크기 구성 가능
  • pasta를 통한 격리된 네트워킹, 기본적으로 호스트 루프백 차단
  • DNS는 기본적으로 호스트 DNS 사용(network.dns로 구성 가능)
  • XDG_RUNTIME_DIR의 특정 소켓만 bind-mount됨(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에는 "특정 인터페이스 거부" 규칙이 없으므로, 이를 좁히려면 Firefox가 필요로 하는 모든 인터페이스를 열거해야 합니다. 기본적으로 그렇게 하지 않는 이유는 docs/DESIGN.md를 참조하세요.
  • 모든 bind 마운트(profile, downloads_dir, 추가 [mounts] bind)는 nosuid,noexec를 사용합니다
  • 브라우저 다운로드는 GPG 서명으로 검증됩니다: Firefox는 Mozilla의 서명된 SHA-512 체크섬, LibreWolf는 LibreWolf 유지관리자의 분리 서명과 함께 제공되는 SHA-256에 대해 검증합니다.
  • 임시 컨테이너(--rm) — 파일시스템 쓰기는 종료 시 손실됩니다
  • 명시적으로 활성화하지 않는 한 호스트 장치(웹캠, 보안 키, 프린터)가 통과되어 전달되지 않습니다

활성화하는 각 [network][mounts] 옵션은 편의를 위해 일부 격리를 맞바꿉니다. 기본값은 여전히 사용 가능한 브라우저를 제공하면서 가장 제한적인 구성입니다.

요구 사항

  • Python 3.11+
  • Podman (루트리스)
  • 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

명명된 케이지와 Firefox 플래그를 결합:```sh ./foxcage @work --kiosk https://example.com

If a cage is already running, the URL opens in a new tab in the existing browser instead of starting a second container. Running `foxcage` (or `foxcage @cage`) with no URL against a running cage exits cleanly with a "cage is already running" message — foxcage can't raise an existing Wayland window from outside the container, so it doesn't try.

Per-launch flags do **not** apply when a cage is already running. `--dns`, `--ipv4-only`, `--lifetime`, `--color` and `--fork` are consumed when the container starts, and a running container's settings can't be changed from outside, so they are ignored with a warning. Close the cage and re-run to apply them.

> 비공개 모드 세션에는 `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입니다. 창이 닫히면 모든 것이 사라집니다 — 쿠키, 캐시, 방문 기록, 확장 프로그램, 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"

이제 모든 임시(ephemeral) 실행은 개인 창, Cloudflare DoH, 그리고 30분 후 자동 종료를 갖게 되며, 완전한 임시성이 유지됩니다. 이름 있는 임시 실행(ephemeral)은 기본적으로 tmp.toml을 상속합니다. 이름별로 재정의하려면 ~/.config/foxcage/tmp-<name>.toml을 생성하세요. 그러면 해당 파일이 tmp.toml 대신 적용됩니다. 병합하지 않으며, 더 구체적인 파일이 완전히 우선합니다. 원하는 경우 공유 기본값을 그 파일에 복사하세요.

일반 cage의 구성에서 설정할 수 있는 모든 항목은 여기서도 작동하지만, 임시성 자체를 무너뜨리는 바로 그 키 하나는 예외입니다:

  • profile — 하드 오류.

이 키는 호스트에 유지되는 영구 프로필 디렉터리를 가리키므로 @tmp의 목적과 직접적으로 모순됩니다. 영구 프로필을 가진 샌드박스 cage를 원한다면 tmp-로 시작하지 않는 일반 이름 있는 cage(@work, @research 등)를 사용하세요.

실행별 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(엄격 모드, cleartext 폴백 없음)으로 설정되고 부트스트랩 주소가 채워져 시작 시 암호화되지 않은 해석 누출이 발생하지 않습니다. stderr에 `Enabling DNS over HTTPS via Cloudflare` 같은 한 줄 알림이 표시됩니다.

내장 별칭:

| 별칭 | 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)는 cleartext 전용으로 유지됩니다. foxcage가 해당 DoH 엔드포인트를 모르기 때문에 DoH는 활성화되지 않습니다. 이런 경우 URI 형식을 사용하세요. `--dns https://pi.hole/dns-query`(유효한 인증서 사용)는 DoH를 활성화하고 컨테이너 DNS는 그대로 둡니다.

URI 형식은 컨테이너의 cleartext DNS 설정을 건너뛰므로 컨테이너 안에서 Firefox가 아닌 모든 것은 여전히 호스트의 DNS를 사용합니다. 이는 의도적인 것입니다. `--dns URI`는 'Firefox가 이 DoH 리졸버를 사용하게 하라'는 뜻이며, 그뿐입니다.

`--dns`는 이미 전체 호스트 네트워크 접근 권한이 있는 `network.mode = "host"`와 호환되지 않습니다.

### 시각적 케이지 식별

이름이 지정된 모든 케이지는 메뉴 바에서 강조 색상을 가지므로 창을 한눈에 구분할 수 있습니다. **아무것도 구성할 필요가 없습니다** — 색상은 케이지 이름에서 결정적으로 파생됩니다(SHA256 해시를 색상(hue)으로 변환하며 채도와 명도는 고정). `@banking`, `@work`, `@personal`, `@tmp-research`는 모두 아무것도 하지 않아도 각각 고유하고 안정적인 색상을 얻습니다.

기본(익명) 케이지는 내장된 주황색을 그대로 유지합니다.

자동 파생 색상을 재정의하려면 명시적으로 설정하세요:```toml
# ~/.config/foxcage/banking.toml
color = "#dc2626"   # red — overrides the auto-derived colour

I received no content to translate. The input chunk is empty. Please provide the source text for chunk 21.```sh ./foxcage @experiment --color "#10b981" https://example.com # teal, one-off

표준 CSS hex를 허용합니다: `#rgb`, `#rrggbb`, 또는 `#rrggbbaa` (알파 포함). 자동 파생 색상은 밝은 메뉴바와 어두운 메뉴바 모두에서 보이도록 조정되어 있습니다(명도 55%, 채도 75% 고정). 따라서 테마 때문에 재정의할 필요는 없습니다.

### 시간 제한 케이지

`--lifetime` 플래그(그리고 동일한 `lifetime` 구성 키)는 설정된 시간이 지나면 케이지를 자동으로 닫습니다. 형식은 `<number><unit>`이며 단위는 `s`, `m`, 또는 `h`입니다:```sh
./foxcage @tmp --lifetime 10m https://example.com
./foxcage @work --lifetime 2h

카운트다운은 Firefox가 실제로 케이지 안에서 실행될 때 시작됩니다 — 컨테이너 시작 시간과 이미지 빌드 시간은 예산에 포함되지 않습니다. 케이지의 메뉴 바 라벨에는 케이지 식별자와 함께 카운트다운이 표시됩니다. 예: FoxCage - tmp (a3f2b1) | 9m — 남은 시간이 1분을 초과하면 분당 한 번, 마지막 1분에는 초당 한 번 업데이트됩니다. 카운트다운이 0에 도달하면 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가 이미지 태그를 재빌드한 후에도 시작 당시의 이미지를 유지합니다. --rebuild, Firefox 업데이트 또는 예약된 재빌드로 이미지가 이후에 업데이트된 케이지에서 탭을 열려고 하면 foxcage는 오류(데스크톱 알림으로도 표시됨)를 표시하며 거부하고 Firefox를 종료한 후 다시 실행하라고 요청합니다. 다시 실행하면 현재 이미지에서 새 컨테이너가 시작됩니다. 활성 케이지에서 --rebuild를 실행하면 foxcage는 미리 경고한 다음 빌드를 수행하고 동일한 검사를 적용합니다.

업데이트

foxcage는 실행할 때마다 새 브라우저 릴리스를 확인합니다. Firefox는 Mozilla의 릴리스 API를, LibreWolf는 GitLab의 releases 엔드포인트를 사용합니다. 업데이트가 있으면 컨테이너 이미지가 자동으로 재빌드됩니다. 또한 Debian 보안 업데이트를 적용하기 위해 이미지가 주기적으로(기본적으로 7일마다) 재빌드됩니다. 업데이트는 이미지 수준에서 처리되므로 브라우저의 내장 자동 업데이터는 비활성화됩니다.

업데이트 확인이 실패하면(네트워크 없음, API 시간 초과) 경고가 출력되고 기존 이미지가 사용됩니다. 언제든지 브라우징할 수 있습니다.

업데이트 주기는 config의 최상위 레벨에 있으며, 버전 및 채널 고정은 포크별 섹션에 있습니다:```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

일치하는 릴리스가 없는 핀은 이제 최신 릴리스로 조용히 대체되는 대신 해당 핀을 명명하는 오류가 됩니다. 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, 원격 측정(telemetry)이 기본적으로 차단되어 있음. GitLab(librewolf-community/browser/bsys6)에서 받은 서명된 Linux tarball로, LibreWolf 유지관리자 키 662E 3CDD 6FE3 2900 2D0C A5BB 4033 9DD8 2B12 EF16에 대한 GPG 검증과 함께 .sha256sum 교차 확인을 수행. 번들된 librewolf.cfg는 보존되며, foxcage는 이를 덮어쓰지 않고 자체 prefs를 그 위에 추가한다.

채널은 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를 실행하면 "새 버전에서 사용됨" 마이그레이션 대화상자가 나타날 수 있습니다. (3) LibreWolf가 제거한 기능(Sync, Pocket, Mozilla 계정)은 조용히 동작하지 않을 뿐 데이터를 손상시키지는 않습니다.

명명된 케이지

자체 config와 Firefox 프로필을 가진 별도의 샌드박스 인스턴스를 실행하세요:```sh ./foxcage @work

This loads `~/.config/foxcage/work.toml` and uses a separate image (`foxcage-work`), container (`foxcage-work`), and volume (`foxcage-work-profile`). The config file must exist for named cages. Cage names may only contain letters, digits, hyphens, and underscores.

## 구성

구성 파일은 `$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 프로필

cage와 호스트 Firefox 프로필을 공유하려면 profile을 프로필 디렉터리로 설정하세요. 호스트의 Firefox에서 about:profiles를 방문해 프로필 경로를 찾거나, cage가 호스트에 유지되는 깨끗한 프로필로 시작하길 원한다면 비어 있는 새 디렉터리를 지정하기만 하면 됩니다.```toml profile = "~/.mozilla/firefox/xxxxxxxx.default-release"

이 한 디렉터리만 케이지에 바인드 마운트됩니다. `~/.mozilla/firefox/` 아래의 형제 프로필과 `profiles.ini` 레지스트리는 노출되지 않으므로, 손상된 케이지는 이를 변조할 수 없습니다.

`profile`이 설정되지 않은 경우, 명명된 Podman 볼륨이 대신 Firefox 프로필을 저장합니다(아래의 "무엇이 유지되는지" 참조). 호스트의 Firefox에서 동일한 프로필이 이미 열려 있으면 Firefox의 프로필별 잠금 파일로 인해 충돌이 발생합니다 — 케이지마다 전용 프로필을 사용하세요.

### 네트워킹

기본적으로 컨테이너는 호스트 루프백이 차단되고 호스트 DNS를 사용하는 pasta를 사용합니다. pasta는 podman 4.4 이상이 필요합니다(podman 5.0부터 루트리스 기본값이 되었습니다).

**호스트 네트워킹**은 네트워크 격리를 완전히 제거합니다. 케이지가 호스트의 `localhost`에 있는 서비스(예: 로컬 개발 서버, `127.0.0.1`의 데이터베이스)에 연결해야 할 때 사용하세요:```toml
[network]
mode = "host"

dnsmode = "host"와 결합할 수 없습니다 — 호스트 네트워킹은 이미 호스트의 리졸버를 사용합니다.

호스트 모드는 localhost만 포기하는 것이 아닙니다. 이 모드는 케이지를 호스트의 네트워크 네임스페이스에 배치하며, 추상 Unix 소켓은 파일시스템이 아닌 해당 네임스페이스로 범위가 지정됩니다. 따라서 호스트 모드의 케이지는 호스트의 추상 주소 소켓에 직접 도달할 수 있습니다. 여기에는 X11 또는 Xwayland를 실행하는 경우 Xwayland의 @/tmp/.X11-unix/X0 (입력 로깅, foxcage가 Wayland 전용임에도 불구하고)와, unix:abstract=…로 구성되어 필터링된 D-Bus 프록시를 우회하는 세션 버스가 포함됩니다. 이는 네트워크 스택을 공유하는 데 따른 본질적인 특성이며, foxcage가 필터링할 수 있는 것이 아닙니다. 호스트 모드가 필요할 때만 사용하고, 그 목적을 위해서만 실행하는 named cage를 선호하십시오.

IPv4 전용 케이지는 IPv6를 완전히 비활성화합니다:```toml [network] ipv4_only = true

또는 실행 시 `--ipv4-only` 플래그(짧은 형식 `-4`, `ssh`/`curl`/pasta에서와 같이)를 사용합니다:```sh
./foxcage @tmp -4 https://example.com

이것은 IPv4 전용 모드(-4)로 pasta를 실행하므로 컨테이너에는 IPv6 스택이 전혀 없으며, 추가로 Firefox에서 network.dns.disableIPv6를 설정하여 AAAA 레코드를 조회하지 않게 합니다. DoH가 활성화된 경우 DoH 응답이 컨테이너의 리졸버를 우회하므로 이는 중요합니다. ipv4_onlymode = "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.

## What persists

Without configuration, a named Podman volume stores the Firefox profile (bookmarks, settings, extensions, Widevine DRM plugin). Everything else is ephemeral.

- Default cage: `foxcage-profile`
- Named cage: `foxcage-<name>-profile`

To start fresh, remove the volume:```sh
podman volume rm foxcage-profile

If profile is set, the host directory is bind-mounted directly and no volume is created.

디스크 사용량

각 케이지 이미지는 약 1GB입니다. 재빌드는 이미지에 다시 태그를 지정하고 이전 이미지를 태그 없는 <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가 없으면 웹사이트가 UTC로 시간을 표시합니다.
  • 로케일. LANG이 전달됩니다. 호스트의 로케일은 빌드 시 컨테이너 이미지에 생성됩니다.

케이지 레이블. 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 sequencer 액세스가 필요합니다. 둘 다 전달되지 않습니다.

**PWA 설치.** Progressive Web Apps는 컨테이너 내부에서 호스트 데스크톱에 설치할 수 없습니다.

**접근성.** AT-SPI를 통한 화면 읽기 프로그램 지원은 비활성화되어 있습니다(`NO_AT_BRIDGE=1`) — 컨테이너는 호스트의 접근성 버스에 연결할 수 없습니다. Web Speech API 합성은 작동합니다. `espeak-ng` 엔진이 포함된 `speech-dispatcher`가 cage에 설치되어 최초 사용 시 자동으로 실행되며, 오디오는 공유 PulseAudio 소켓을 통해 라우팅됩니다.

## 호스트 구성

### 권장: fuse-overlayfs를 사용한 overlay 스토리지

루트리스 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` is set in the `.desktop` file, which tells the compositor to show a spinner cursor while foxcage starts. When an image build is needed (which can take several minutes), foxcage sends a desktop notification so you know Firefox is on its way. Any early-exit error (config typo, missing dependency, malformed cage name) is also surfaced as a desktop notification so desktop-launched users aren't left staring at nothing when foxcage fails without a terminal attached. Both require `notify-send` (from `libnotify-bin` on Debian/Ubuntu) — if it is not installed, notifications are silently skipped and the error still goes to stderr.

<details>
<summary>Manual setup</summary>

If you prefer to create the `.desktop` file manually, create `~/.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>

## 테스트 실행

테스트 스위트는 pytest + pytest-cov를 사용하며, `requirements-dev.txt`에 개발 전용 의존성으로 선언되어 있습니다.```
pip install -r requirements-dev.txt
pytest

테스트는 완전히 격리되어 있습니다 — podman 없음, 네트워크 없음, pytest의 tmp_path 외 실제 파일시스템 없음. 이 스위트는 100% 라인 및 브랜치 커버리지에서 게이트를 겁니다(pytest.ini.coveragerc에 구성됨). 커버되지 않은 라인이나 실행되지 않은 조건문 분기가 하나라도 있으면 실행이 실패합니다. CI는 .gitlab-ci.yml을 통해 모든 푸시마다 스위트를 실행합니다.

감사의 글

이 프로젝트는 Mike Cardwell이 개발했으며, Anthropic의 AI 코딩 도구인 Claude Code의 도움을 받았습니다.

제 작업을 지원/감상해 주세요

카테고리