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

foxcage — Updated!

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

공유

foxcage icon foxcage

보안 격리를 위해 rootless 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)는 바인드 마운트하지 않은 한 아무것도 지속되지 않음을 의미합니다.
  • 측면 네트워크 이동. 기본적으로 컨테이너는 localhost의 서비스를 탐색할 수 없습니다. 일반 Firefox에서는 샌드박스 탈출이 전체 네트워크 접근 권한을 갖습니다. (케이지가 localhost 접근을 필요로 하는 경우 — 예를 들어 로컬 개발을 위해 — [network] mode = "host"를 사용하세요. 단, "네트워킹" 아래의 주의사항을 참고하세요: 호스트 모드는 호스트의 추상 Unix 소켓도 노출합니다.)
  • 권한 상승. 컨테이너는 CAP_SYS_CHROOT를 제외한 모든 Linux capability를 제거하고 새로운 권한 획득을 차단합니다. Setuid 바이너리, 난해한 시스템 콜을 통한 커널 익스플로잇 및 유사한 상승 경로가 차단됩니다.

foxcage가 방어하지 않는 것

  • 브라우저 수준 공격. 피싱, 악성 확장 기능, 그리고 Firefox의 정상 기능 내에서 작동하는 모든 것은 영향을 받지 않습니다 — foxcage는 컨테이너를 호스트로부터 격리할 뿐, 사용자를 브라우저로부터 격리하지 않습니다.
  • 바인드 마운트된 디렉터리. 마운트하는 모든 것(profile, downloads_dir, 추가 바인드 마운트)은 침해된 브라우저가 완전히 접근할 수 있습니다. 호스트 프로필 디렉터리를 마운트하면 공격자는 일반 Firefox에서와 마찬가지로 이를 변조할 수 있습니다.
  • PulseAudio를 통한 오디오 캡처. PulseAudio 소켓은 컨테이너에 바인드 마운트됩니다. 파일 시스템 수준에서는 읽기 전용으로 마운트되지만, Unix 도메인 소켓은 양방향이므로 침해된 프로세스가 여전히 소켓을 통해 녹음 요청을 보낼 수 있습니다. 브라우저 샌드박스 탈출은 잠재적으로 호스트 마이크에서 오디오를 녹음할 수 있습니다.
  • D-Bus를 통한 다른 Firefox들. 실행 중인 케이지에서 링크를 여는 것은 Firefox의 D-Bus 원격 제어에 의존하며, 프록시는 이를 위해 전체 org.mozilla.* 네임스페이스를 허용해야 합니다(케이지별 정확한 이름은 안전하게 예측할 수 없습니다). 따라서 침해된 케이지는 다른 케이지에서 해당 쿠키와 함께, 또는 호스트에서 실행 중인 Firefox에서 URL을 열 수 있습니다. 호스트 Firefox가 실행 중이 아닐 때는 그 D-Bus 이름을 차지하고 그에게 향한 링크를 받을 수도 있습니다. docs/DESIGN.md의 "D-Bus proxy"를 참고하세요.
  • Wayland 컴포지터 익스플로잇. Wayland 소켓은 그대로 전달됩니다. Wayland 컴포지터는 설계상 클라이언트를 서로 격리하지만, 컴포지터 자체의 취약점은 도달 가능합니다.

보안 구성

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

  • 모든 Linux capability 제거(Firefox의 콘텐츠 샌드박스를 위해 CAP_SYS_CHROOT만 다시 추가, init.root가 설정된 경우 CAP_SETUID/CAP_SETGID를 일시적으로 추가)
  • 권한 상승을 방지하는 no-new-privileges
  • 루트리스 사용자 네임스페이스(--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에는 "특정 인터페이스 거부" 규칙이 없으므로 이를 좁히려면 Firefox가 필요로 하는 모든 인터페이스를 열거해야 합니다. 기본적으로 이렇게 하지 않는 이유는 docs/DESIGN.md를 참조하십시오
  • 모든 바인드 마운트(profile, downloads_dir, 추가 [mounts] bind)는 nosuid,noexec 사용
  • 브라우저 다운로드는 GPG 서명으로 검증됨: Firefox는 Mozilla의 서명된 SHA-512 체크섬으로, LibreWolf는 LibreWolf Maintainers의 분리 서명과 함께 제공되는 SHA-256으로 검증. 검증은 gpg --verify보다 엄격한데, gpg --verify는 폐기된 키로 만들어진 서명과 키링의 모든 키에 대해 종료 코드 0을 반환합니다. foxcage는 추가로 서명이 고정된 기본 키로 이어지는 체인을 요구하며, 소유자가 손상된 것으로 폐기한 서브키로 서명된 릴리스는 거부합니다 — 폐기된 서명 키 참조
  • 임시 컨테이너(--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는 소프트웨어로 렌더링합니다. Intel, AMD, nouveau용 VA-API 드라이버가 이미지에 설치되어 있으므로 호스트 드라이버 패키지 없이 하드웨어 비디오 디코딩이 작동합니다 — 하드웨어 비디오 디코딩 참조

foxcage는 root나 sudo가 아닌 일반 데스크톱 사용자로 실행하십시오. 샌드박스는 사용자를 컨테이너에 매핑하며, root로 실행하면 foxcage가 제공하려는 격리가 제거됩니다. root로는 시작을 거부합니다.

테스트 환경: GNOME 3가 설치된 Debian 13(Trixie). 다른 Linux 배포판과 Wayland 컴포지터는 작동할 수 있지만 테스트되지 않았습니다.

설치

foxcage는 Python 표준 라이브러리 외에 의존성이 없는 단일 Python 스크립트입니다. PATH에 있는 디렉터리로 복사하십시오:

sudo cp foxcage /usr/local/bin/foxcage

또는 사용자 로컬 설치의 경우:

cp foxcage ~/.local/bin/foxcage

스크립트가 실행 가능한지 확인하세요 (chmod +x foxcage).

foxcage --version으로 어떤 리비전을 가지고 있는지 확인하세요 — foxcage는 단일 파일을 복사하여 설치되므로 문제를 보고할 때 유용합니다.

사용법

./foxcage

첫 실행 시 스크립트는 컨테이너 이미지를 빌드하고(Mozilla에서 Firefox를 다운로드하고 최소한의 Debian 의존성을 설치) 그다음 Firefox를 시작합니다. 이후 실행에서는 foxcage가 Firefox 업데이트를 확인하고 새 버전이 있으면 이미지를 자동으로 다시 빌드합니다. 또한 시스템 패키지 업데이트를 반영하기 위해 이미지를 주기적으로(기본값 7일마다) 다시 빌드합니다. 업데이트 확인이 실패하면(네트워크 오류, 시간 초과) 경고가 기록되고 기존 이미지가 사용됩니다 — 시작이 차단되는 일은 결코 없습니다.

Firefox에 인자를 전달합니다:

./foxcage https://example.com

지정된 cage와 Firefox 플래그를 결합합니다:

./foxcage @work --kiosk https://example.com

foxcage를 실행한 터미널을 닫아도 브라우저는 계속 실행됩니다. foxcage는 백그라운드에 남아 Firefox가 종료되면 정리합니다. 스크립트에서 cage를 닫으려면 foxcage에 SIGTERM을 보내세요. Ctrl+C와 동일하게 컨테이너를 정상적으로 중지합니다.

cage가 이미 실행 중이면 URL은 두 번째 컨테이너를 시작하는 대신 기존 브라우저의 새 탭에서 열립니다. 이는 cage가 아직 시작 중이거나 빌드 중일 때도 마찬가지입니다. 그동안 클릭한 링크는 ("Waiting for another launch of cage ... to start") 대기한 후 경쟁하는 컨테이너를 시작하는 대신 해당 cage의 탭으로 열립니다. 실행 중인 cage에 대해 URL 없이 foxcage(또는 foxcage @cage)를 실행하면 "cage is already running" 메시지와 함께 정상적으로 종료됩니다. foxcage는 컨테이너 외부에서 기존 Wayland 창을 띄울 수 없으므로 시도하지 않습니다.

cage가 이미 실행 중일 때는 실행별 플래그가 적용되지 않습니다. --dns, --ipv4-only, --lifetime, --color, --fork는 컨테이너가 시작될 때 소비되며, 실행 중인 컨테이너의 설정은 외부에서 변경할 수 없으므로 경고와 함께 무시됩니다. 적용하려면 cage를 닫고 다시 실행하세요.

프라이빗 모드 세션에는 Firefox의 원시 --private-window CLI 플래그가 아니라 private_browsing 설정 키를 사용하세요. 이 설정 키는 세션 전체 프라이빗 모드(browser.privatebrowsing.autostart)를 설정하므로 이후 foxcage @cage URL 호출이 탭에서 다시 열릴 수 있습니다. Firefox 패스스루로서의 --private-window는 첫 번째 창만 프라이빗으로 만들고 위의 탭 재열기 동작을 깨뜨립니다.

주의: private_browsing = true로 활성화된 세션은 Firefox의 일반적인 프라이빗 창 UI 표시(보라색 강조 표시줄, 마스크 아이콘, 제목의 "(Private Browsing)")를 보여주지 않습니다. 세션의 모든 창이 프라이빗이므로 Firefox가 시각적으로 대비할 비프라이빗 창이 없어 표시기를 억제하기 때문입니다. 세션은 실제로 프라이빗입니다. 원한다면 cage에서 about:privatebrowsing을 방문하거나(표준 Private Browsing 정보 페이지 표시) about:config에서 browser.privatebrowsing.autostart = true를 확인하여 검증할 수 있습니다.

@tmp를 사용한 임시 브라우징

흔적을 남기지 않아야 하는 일회성 링크에는 예약된 tmp cage를 사용하세요:

./foxcage @tmp https://somewhere-suspicious.example

@tmp 실행은 매번 영구 프로필이 없는 일회용 Firefox를 새로 띄운다. 창을 닫으면 쿠키, 캐시, 방문 기록, 확장 기능, HSTS 상태, TLS 세션 캐시, DNS 캐시, 저장된 탭 상태 등 모든 것이 사라진다. 이는 확장 기능과 상당량의 디스크 상태를 여전히 유지하는 Firefox 사생활 보호 모드보다 더 나아간 것이다.

여러 @tmp 케이지를 동시에 실행할 수 있으며, 각각은 서로 격리된다. 메뉴 바에 FoxCage - tmp (<short id>)가 표시되므로 동시에 열린 임시 창들을 구분할 수 있다.

임시 케이지는 시작 시 빈 페이지를 열고 새 탭도 빈 페이지로 연다. 기본 Firefox 홈 페이지와 새 탭 콘텐츠(상위 사이트, Pocket 추천, 활동 스트림)는 곧 버려질 새 프로필에서는 순전한 잡음일 뿐이므로 억제된다. 영구 케이지는 Firefox의 기본값을 유지한다.

이름이 지정된 임시 케이지

일회용 세션에 의미 있는 이름을 붙이고 싶다면(예를 들어 새 탭에서 다시 열고 싶은 리서치 탐구), @tmp-<name>을 사용한다:

카테고리